﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using alib.Character;
using alib.Collections.ReadOnly;
using alib.Enumerable;
using alib.String;

using agree.schema;

namespace agree.configuration
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class config_loader_base : m_scalar_base<String, AgreeConfig>,
		ρρFilename
	{
		public config_loader_base(IRtParent parent, String s_file)
			: base(parent, s_file)
		{
		}

		protected override void ValidateArg0(ref string s_files)
		{
			base.ValidateArg0(ref s_files);

			var dps = (DirectoryPathSet)PP.DefaultDirectories.FindTracePropertyValue(this);

			var fns = s_files.Split(';');
			for (int i = 0; i < fns.Length; i++)
			{
				var x = fns[i];
				if (x == null)
					throw new FileNotFoundException("config_loader_base", x);
				if (dps != null)
					x = dps.FindGrammarFile(fns[i]);
				fns[i] = x;
			}
			s_files = fns.StringJoin(";");
		}

		///////////////////////////////////////////////////////////////////////
		/// 
		public String Filename
		{
			get { return base.t0; }
			set { base.t0 = value; }
		}
		/// 
		///////////////////////////////////////////////////////////////////////

		protected override AgreeConfig start(String filename)
		{
			if (String.IsNullOrWhiteSpace(filename))
				throw new FileNotFoundException();

			AgreeConfig _config = new AgreeConfig(this);

			ApplyConfig(_config, filename);

			return _config;
		}

		public abstract void ApplyConfig(AgreeConfig _config, String filename);
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// other PET-style options not handled here
	///		add key="opt_filter" value="true"
	///		add key="opt_key" value="KeyDriven"
	///			KeyDriven, LeftToRight, RightToLeft, HeadDriven -->
	///		add key="opt_sm" value="none"
	///		add key="opt_chart_pruning" value="false"
	///		add key="opt_chart_pruning_strategry" value="2"
	///			0=all tasks; 1=all successful tasks; 2=all passive items
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class PetConfigLoader : config_loader_base
	{
		public PetConfigLoader(IRtParent parent, String filename)
			: base(parent, filename)
		{
		}
		public PetConfigLoader(IRtParent parent)
			: this(parent, default(String))
		{
		}

		public override void ApplyConfig(AgreeConfig _config, String filename)
		{
			_config.Types.TopType = "top";
			_config.Types.TypesList = "list";
			_config.Types.TypesEmptyList = "nil";
			_config.Types.TypesDiffList = "difflist";

			_config.Grammar.OrthPath = new BootstrapFsPath(this, "orth");

			ReadConfigFile(_config, filename);
		}

		public void ReadConfigFile(AgreeConfig _config, String filename)
		{
			StringBuilder sb = new StringBuilder();
			int line_no = 0;
			using (StreamReader sr = new StreamReader(filename))
			{
				String r_line;
				while ((r_line = sr.ReadLine()) != null)
				{
					line_no++;
					int ix = r_line.QuoteInsulatedIndexOf(';');
					if (ix != -1)
						r_line = r_line.Remove(ix);
					r_line = r_line.Trim();
					if (r_line != String.Empty)
						sb.Append(" ;" + line_no.ToString() + " " + r_line.Select(ch => ch < ' ' ? ' ' : ch).NewString());
				}
			}
			foreach (String stmt in sb.QuoteInsulatedSplit('.'))
			{
				String cur_file_dir = Path.GetDirectoryName(filename);
				String[] parts = stmt.Split(Charset.sp, StringSplitOptions.RemoveEmptyEntries);
				while (parts.Length > 0 && parts[0][0] == ';')
				{
					line_no = int.Parse(parts[0].Substring(1));
					parts = parts.Skip(1).ToArray();
				}
				String kw = parts[0].ToLower();
				if (kw == "include")
				{
					if (parts.Length != 2)
						throw new ConfigException("Error: '{0}.' in {1}, line {2}", parts.StringJoin(" "), filename, line_no);

					String inc_file = parts[1].Trim(Charset.sq_dq);
					if (!Path.HasExtension(inc_file))
						inc_file = Path.ChangeExtension(inc_file, ".set");
					String try_file = Path.Combine(cur_file_dir, inc_file);
					if (File.Exists(try_file))
					{
						ReadConfigFile(_config, try_file);
						continue;
					}
					if (cur_file_dir != Environment.CurrentDirectory)
					{
						try_file = Path.Combine(Environment.CurrentDirectory, inc_file);
						if (File.Exists(try_file))
						{
							ReadConfigFile(_config, try_file);
							continue;
						}
					}
					throw new ConfigException("Error: include file '{0}' not found in {1}, line {2}", inc_file, line_no);
				}
				SetGlobalVar(_config, filename, line_no, parts.Where(s => s[0] != ';').ToArray());
			}
		}

		void SetGlobalVar(AgreeConfig _config, String filename, int line_no, String[] rgs)
		{
			String kw = rgs[0].ToLower();
			String arg = null;
			String[] args = null;
			if (rgs.Length > 1)
			{
				if (rgs[1] != ":=")
					throw new ConfigException("Error: expected ':=' in {0}, line {1}", filename, line_no);
				if (rgs.Length == 2)
				{
					/// foo := .	(Jacy) Note that we are leaving whatever default value, as opposed to enforcing the empty string.
					return;
					//throw new ConfigException("Error: nothing specified on the right of ':=' in {0}, line {1}", file, line_no);
				}
				args = rgs.Skip(2).ToArray();
				arg = args[0];
			}
			else
				arg = rgs[0];

			arg = arg.Trim(Charset.sq_dq).ToLower();

			switch (kw)
			{
				case "encoding":
					break;

				case "irregular-forms-only":
					if (args != null)
						throw new ConfigException("Error: 'irregular-forms-only' is a boolean option; in {0}, line {1}", filename, line_no);
					_config.Morphology.IrregularFormsOnly = true;
					break;

				case "case-sensitive":
					if (args != null)
						throw new ConfigException("Error: 'case-sensitive' is a boolean option; in {0}, line {1}", filename, line_no);
					_config.Lexicon.LexicalComparison = StringComparer.Ordinal;
					break;

				case "lex-entries-can-fail":
					if (args != null)
						throw new ConfigException("Error: 'lex-entries-can-fail' is a boolean option; in {0}, line {1}", filename, line_no);
					_config.Morphology.IsLexEntriesCanFail = true;
					break;

				case "unidirectional-chart-dependencies":
					if (args != null)
						throw new ConfigException("Error: 'unidirectional-chart-dependencies' is a boolean option; in {0}, line {1}", filename, line_no);
					_config.LexDependencies.ChartDependencyScope = LexDependenciesConfig.DependencyScope.Unidirectional;
					break;

				case "chart-dependencies":
					_config.LexDependencies.BootstrapChartDependencyPaths = rgs.Skip(2).PairOff().ToArray();
					break;

				case "special-name-top":
					if (args.Length > 1)
						throw new ConfigException("Error: cannot define more than one instance of the special 'top' type; in {0}, line {1}", filename, line_no);
					_config.Types.TopType = arg;
					break;

				case "special-name-cons":
					/* don't know why this is needed */
					break;

				case "special-name-symbol":
					/* don't know what this is for */
					break;

				case "special-name-string":
					if (args.Length > 1)
						throw new ConfigException("Error: cannot define more than one instance of the special 'string' type; in {0}, line {1}", filename, line_no);
					_config.Types.StringType = arg;
					break;

				case "special-name-list":
					if (args.Length > 1)
						throw new ConfigException("Error: cannot define more than one instance of the special 'list' type; in {0}, line {1}", filename, line_no);
					_config.Types.TypesList = arg;
					break;

				case "special-name-nil":
					if (args.Length > 1)
						throw new ConfigException("Error: cannot define more than one instance of the special 'empty list' type; in {0}, line {1}", filename, line_no);
					_config.Types.TypesEmptyList = arg;
					break;

				case "special-name-difflist":
					if (args.Length > 1)
						throw new ConfigException("Error: cannot define more than one instance of the special 'difference list' type; in {0}, line {1}", filename, line_no);
					_config.Types.TypesDiffList = arg;
					break;

				case "special-name-attr-first":
					if (args.Length > 1)
						throw new ConfigException("Error: cannot define more than symbol for the special 'list head' feature; in {0}, line {1}", filename, line_no);
					_config.Types.ListHead = arg;
					break;

				case "special-name-attr-rest":
					if (args.Length > 1)
						throw new ConfigException("Error: cannot define more than symbol for the special 'list tail' feature; in {0}, line {1}", filename, line_no);
					_config.Types.ListTail = arg;
					break;

				case "special-name-attr-list":
					if (args.Length > 1)
						throw new ConfigException("Error: cannot define more than symbol for the special 'difference list head' feature; in {0}, line {1}", filename, line_no);
					_config.Types.DlistList = arg;
					break;

				case "special-name-attr-last":
					if (args.Length > 1)
						throw new ConfigException("Error: cannot define more than symbol for the special 'difference list tail' feature; in {0}, line {1}", filename, line_no);
					_config.Types.DlistLast = arg;
					break;

				case "special-name-attr-args":
					/* don't know if/why this is different from rule-args-path */
					break;

				case "rule-args-path":
					if (args.Length > 1)
						throw new ConfigException("Error: cannot define more than symbol for the rule arguments path; in {0}, line {1}", filename, line_no);
					_config.Grammar.RuleArgsPath = new BootstrapFsPath(this, arg);
					break;

				case "keyarg-marker-path":
					if (args.Length > 1)
						throw new ConfigException("Error: cannot define more than symbol for the key daughter path; in {0}, line {1}", filename, line_no);
					_config.Grammar.KeyDaughterPath = new BootstrapFsPath(this, arg);
					break;

				case "true-type":
					if (args.Length > 1)
						throw new ConfigException("Error: cannot define more than symbol for the key daughter asserted type; in {0}, line {1}", filename, line_no);
					_config.Grammar.KeyDaughterValue = arg;
					break;

				case "orth-path":
					if (args.Length > 1)
						throw new ConfigException("Error: cannot define more than one orthography path; in {0}, line {1}", filename, line_no);
					_config.Grammar.OrthPath = new BootstrapFsPath(this, arg);
					break;

				case "head-dtr-path":
					if (args.Length > 1)
						throw new ConfigException("Error: cannot define more than one head daughter path; in {0}, line {1}", filename, line_no);
					_config.Grammar.HeadDaughterPath = new BootstrapFsPath(this, arg);
					break;
				case "deleted-daughters":
					_config.Grammar.DeletedDaughters.UnionWith(args.Select(s => s.ToLower()));
					break;

				case "packing-restrictor":
					_config.Parser.Chart.PackingRestrictors.UnionWith(args.Select(s => s.Trim()));
					break;

				case "start-symbols":
					_config.Parser.Chart.StartSymbols.UnionWith(args.Select(s => s.ToLower().TrimStart('$')));
					break;

				case "non-idiom-root":
					_config.Parser.Chart.NonIdiomRoot = arg;
					break;

				case "spanning-only-rules":
					_config.Grammar.SpanOnlyRules.UnionWith(args.Select(s => s.ToLower().TrimStart('$')));
					break;
				case "punctuation-characters":
					if (_config.Parser.PunctuationChars == null)
						_config.Parser.PunctuationChars = new HashSet<Char>();
					_config.Parser.PunctuationChars.UnionWith(arg);
					break;

				case "postload-lisp-files":
				case "preload-lisp-files":
					/* we will do our own MRS extraction */
					break;

				case "lexicon-tokens-path":
					_config.TokenMapping.LexiconTokensPath = new BootstrapFsPath(this, arg);
					break;
				case "lexicon-last-token-path":
					_config.TokenMapping.LexiconLastTokenPath = new BootstrapFsPath(this, arg);
					break;
				case "token-form-path":
					_config.TokenMapping.TokenFormPath = new BootstrapFsPath(this, arg);
					break;
				case "token-carg-path":
					_config.TokenMapping.TokenCargPath = new BootstrapFsPath(this, arg);
					break;
				case "token-id-path":
					_config.TokenMapping.TokenIdPath = new BootstrapFsPath(this, arg);
					break;
				case "token-from-path":
					_config.TokenMapping.TokenFromPath = new BootstrapFsPath(this, arg);
					break;
				case "token-to-path":
					_config.TokenMapping.TokenToPath = new BootstrapFsPath(this, arg);
					break;
				case "token-postags-path":
					_config.TokenMapping.TokenPostagsPath = new BootstrapFsPath(this, arg);
					break;
				case "token-posprobs-path":
					_config.TokenMapping.TokenPosprobsPath = new BootstrapFsPath(this, arg);
					break;

				case "chart-mapping-context-path":
					_config.TokenMapping.ChartMappingContextPath = new BootstrapFsPath(this, arg);
					break;
				case "chart-mapping-input-path":
					_config.TokenMapping.ChartMappingInputPath = new BootstrapFsPath(this, arg);
					break;
				case "chart-mapping-output-path":
					_config.TokenMapping.ChartMappingOutputPath = new BootstrapFsPath(this, arg);
					break;
				case "chart-mapping-position-path":
					_config.TokenMapping.ChartMappingPositionPath = new BootstrapFsPath(this, arg);
					break;

				/// NODE LABELS
				case "pn-label-path":
					_config.NodeLabels.LabelPath = new BootstrapFsPath(this, arg);
					break;
				case "pn-prefix-path":
					_config.NodeLabels.PrefixPath = new BootstrapFsPath(this, arg);
					break;
				case "pn-suffix-path":
					_config.NodeLabels.SuffixPath = new BootstrapFsPath(this, arg);
					break;
				case "pn-recursive-path":
					_config.NodeLabels.RecursivePath = new BootstrapFsPath(this, arg);
					break;
				case "pn-local-path":
					_config.NodeLabels.LocalPath = new BootstrapFsPath(this, arg);
					break;
				case "pn-label-fs-path":
					_config.NodeLabels.LabelFsPath = new BootstrapFsPath(this, arg);
					break;
				case "pn-label-type":
					_config.NodeLabels.LabelTemplateType = arg;
					break;
				case "pn-meta-type":
					_config.NodeLabels.MetaTemplateType = arg;
					break;

				case "lex-keyrel-path":		// not a PET option
					_config.Mrs.LexKeyrelPath = new BootstrapFsPath(this, arg);
					break;

				/// MRS
				case "sem-relation-suffix":
				case "mrs-relation-suffix":	/* JACY typo? */
					_config.Mrs.RelationSuffix = arg;
					break;

				case "mrs-initial-semantics-path":
					_config.Mrs.InitialSemanticsPath = new BootstrapFsPath(this, arg);
					break;
				case "mrs-psoa-liszt-path":
					_config.Mrs.RelsPath = new BootstrapFsPath(this, arg);
					break;
				case "mrs-psoa-rh-cons-path":
					_config.Mrs.HconsPath = new BootstrapFsPath(this, arg);
					break;
				case "mrs-psoa-icons-path":
					_config.Mrs.IconsPath = new BootstrapFsPath(this, arg);
					break;
				case "mrs-psoa-top-h-path":
					_config.Mrs.LtopPath = new BootstrapFsPath(this, arg);
					break;
				case "mrs-psoa-index-path":
					_config.Mrs.IndexPath = new BootstrapFsPath(this, arg);
					break;

				case "mrs-sc-arg-feature":
					_config.Mrs.HargFeat = arg;
					break;
				case "mrs-outscpd-feature":
					_config.Mrs.LargFeat = arg;
					break;

				case "mrs-rel-cfrom-feature":
					_config.Mrs.CFrom = arg;
					break;
				case "mrs-rel-cto-feature":
					_config.Mrs.CTo = arg;
					break;
				case "mrs-value-feats":
					_config.Mrs.ValueFeats = args;
					break;
				case "mrs-ignored-sem-features":
					_config.Mrs.IgnoredSemFeatures = args;
					break;
				case "mrs-ignored-extra-features":
					_config.Mrs.IgnoredExtraFeatures = args;
					break;

				case "mrs-rel-name-path":
					_config.Mrs.RelPredPath = new BootstrapFsPath(this, arg);
					break;
				case "mrs-rel-handel-path":
					_config.Mrs.RelLblPath = new BootstrapFsPath(this, arg);
					break;
				case "mrs-feat-priority-list":
					_config.Mrs.FeatPriorityList = args.Select(s => s.ToLower()).ToArray();
					break;
				case "mrs-extra-priority-list":
					_config.Mrs.ExtraPriorityList = args.Select(s => s.ToLower()).ToArray();
					break;

				default:
					//Console.WriteLine("{0} {1}", kw, args.StringJoin("\t"));
					break;
			}
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class LkbConfigLoader : config_loader_base
	{
		public LkbConfigLoader(IRtParent parent, String filenames)
			: base(parent, filenames)
		{
		}
		public LkbConfigLoader(IRtParent parent)
			: this(parent, default(String))
		{
		}

		public override void ApplyConfig(AgreeConfig _config, String filename)
		{
			foreach (var fn in filename.Split(';'))
				foreach (Pairing<String> tuple in LispExt.GetTuples(fn))
					SetGlobalVar(_config, fn, tuple.x, tuple.y);
		}

		void SetGlobalVar(AgreeConfig _config, String fn, String param, String value)
		{
			param = param.RemovePrefix("lkb::", StringComparison.OrdinalIgnoreCase);

			if (value.Length > 0 && (value[0] == '\'' || value[0] == '`'))
				value = value.Substring(1);
			if (value.Length > 0 && value[0] == '(' && value[value.Length - 1] == ')')
				value = value.Substring(1, value.Length - 2);

			if (param.Length == 0 || param[0] != '*' || param[param.Length - 1] != '*')
			{
				Debug.Print("Skipping unrecognized parameter: {0}", param);
				return;
			}
			param = param.Substring(1, param.Length - 2);

			int i;

			switch (param)
			{
				case "start-symbol":
					_config.Parser.Chart.StartSymbols.Add(value);
					break;
				case "non-idiom-root":
					_config.Parser.Chart.NonIdiomRoot = value;
					break;

				case "toptype":
					_config.Types.TopType = value;
					break;
				case "string-type":
					_config.Types.StringType = value;
					break;
				case "orth-path":
					_config.Grammar.OrthPath = new BootstrapFsPath(this, value);
					break;

				case "list-type":
					_config.Types.TypesList = value;
					break;
				case "list-tail":
					_config.Types.ListTail = value;
					break;
				case "list-head":
					_config.Types.ListHead = value;
					break;
				case "empty-list-type":
					_config.Types.TypesEmptyList = value;
					break;

				case "diff-list-type":
					_config.Types.TypesDiffList = value;
					break;
				case "diff-list-list":
					_config.Types.DlistList = value;
					break;
				case "diff-list-last":
					_config.Types.DlistLast = value;
					break;
				case "deleted-daughter-features":
					_config.Grammar.DeletedDaughters.UnionWith(value.Split(Charset.sp, StringSplitOptions.RemoveEmptyEntries));
					break;
				case "active-parsing-p":
					/* not relevant; ignored */
					break;

				case "irregular-forms-only-p":
					_config.Morphology.IrregularFormsOnly = value == "t";
					break;

				case "chart-packing-p":
					if (value == "t")
						_config.Parser.Chart.Packing = ChartConfig.PackingOpts.Full;
					else if (value == "nil")
						_config.Parser.Chart.Packing = ChartConfig.PackingOpts.None;
					break;

				case "packing-restrictor":
					_config.Parser.Chart.PackingRestrictors.UnionWith(value.Split(Charset.sp, StringSplitOptions.RemoveEmptyEntries));
					break;

				case "check-paths":
					{
						int ix = 0;
						while ((ix = value.IndexOf("  ", ix)) != -1)
							value = value.Remove(ix, 1);
						if (value.StartsWith("quote (") && value.EndsWith(")"))
							value = value.Substring(7, value.Length - 8);

						String[] all_paths = value.Split(new[] { ") (" }, StringSplitOptions.None);
						if (all_paths.Length == 0)
							break;

						String s_tmp = all_paths[0];
						if (s_tmp.Length > 0 && s_tmp[0] == '(')
							all_paths[0] = s_tmp.Substring(1);

						s_tmp = all_paths[all_paths.Length - 1];
						if (s_tmp.Length > 0 && s_tmp.EndsWith(")"))
							all_paths[all_paths.Length - 1] = s_tmp.Remove(s_tmp.Length - 1);

						_config.Parser.Chart.Unification.QuickCheckPaths.UnionWith(all_paths.Select(qcp => qcp.ExtractParenthesized()));
					}
					break;

				case "punctuation-characters":
					{
						if (_config.Parser.PunctuationChars == null)
							_config.Parser.PunctuationChars = new HashSet<Char>();
						foreach (String _s in value.Split(Charset.sp, StringSplitOptions.RemoveEmptyEntries))
						{
							if (_s == "append")
								continue;
							String s = _s;
							if (s.StartsWith("'("))
								s = s.Substring(2);
							if (s.StartsWith(@"#\"))
							{
								if (s.Length > 3)
									s = s.TrimEnd(')');
								s = s.Substring(2);
								if (s.Length == 1)
									_config.Parser.PunctuationChars.Add(s[0]);
								else
									_config.Parser.PunctuationChars.Add(LispExt.lisp_char_codes[s]);
							}
						}
					}
					break;

				case "chart-limit":
					if (!int.TryParse(value, out i))
						throw new Exception("Error: parsing 'chart-limit', expected an integer");
					else
						_config.Parser.Chart.ColumnLimit = i;
					break;
				case "maximum-number-of-edges":
					if (!int.TryParse(value, out i))
						throw new Exception("Error: parsing 'maximum-number-of-edges', expected an integer");
					else
						_config.Parser.Chart.MaxEdges = i;
					break;

				case "maximal-lex-rule-applications":
					if (!int.TryParse(value, out i))
						throw new Exception("Error: parsing 'maximum-number-of-edges', expected an integer");
					else
						_config.Morphology.MaximumLexRuleApplications = i;
					break;

				case "key-daughter-path":
					_config.Grammar.KeyDaughterPath = new BootstrapFsPath(this, value);
					break;

				case "key-daughter-type":
					_config.Grammar.KeyDaughterValue = value;
					break;

				/// Node label configuration
				case "simple-tree-display":
					_config.NodeLabels.SimpleTreeDisplay = value == "t";
					break;
				case "label-path":
					_config.NodeLabels.LabelPath = new BootstrapFsPath(this, value);
					break;
				case "prefix-path":
					_config.NodeLabels.PrefixPath = new BootstrapFsPath(this, value);
					break;
				case "suffix-path":
					_config.NodeLabels.SuffixPath = new BootstrapFsPath(this, value);
					break;
				case "local-path":
					_config.NodeLabels.LocalPath = new BootstrapFsPath(this, value);
					break;
				case "recursive-path":
					_config.NodeLabels.RecursivePath = new BootstrapFsPath(this, value);
					break;
				case "label-fs-path":
					_config.NodeLabels.LabelFsPath = new BootstrapFsPath(this, value);
					break;
				case "label-template-type":
					_config.NodeLabels.LabelTemplateType = value;
					break;

				/// MRS
				case "initial-semantics-path":
					_config.Mrs.InitialSemanticsPath = GetVsymPath(value);
					break;
				case "construction-semantics-path":
					_config.Mrs.CContPath = GetVsymPath(value);
					break;
				case "psoa-liszt-path":
					_config.Mrs.RelsPath = GetVsymPath(value);
					break;
				case "psoa-rh-cons-path":
					_config.Mrs.HconsPath = GetVsymPath(value);
					break;
				case "psoa-icons-path":
					_config.Mrs.IconsPath = GetVsymPath(value);
					break;
				case "psoa-top-h-path":
					_config.Mrs.LtopPath = GetVsymPath(value);
					break;
				case "psoa-event-path":
				case "psoa-index-path":
					{
						var fsp = GetVsymPath(value);
						if (_config.Mrs.IndexPath == null)
							_config.Mrs.IndexPath = fsp;
						else if (!_config.Mrs.IndexPath.Equals(fsp))
							throw new Exception("'psoa-event-path' is superseded by 'psoa-index-path'. Cannot specify different values for both.");
					}
					break;
				case "lbl-feature":
					_config.Mrs.LblFeat = value.GetVsymString();
					break;
				case "bv-feature":
					_config.Mrs.Arg0Feat = value.GetVsymString();
					break;
				case "scope-feat":
					_config.Mrs.BodyFeat = value.GetVsymString();
					break;
				case "sc-arg-feature":
					_config.Mrs.HargFeat = value.GetVsymString();
					break;
				case "outscpd-feature":
					_config.Mrs.LargFeat = value.GetVsymString();
					break;
				case "value-feats":
					_config.Mrs.ValueFeats = value.GetVsymStrings().ToArray();
					break;
				case "ignored-sem-features":
					_config.Mrs.IgnoredSemFeatures = value.GetVsymStrings().ToArray();
					break;
				case "ignored-extra-features":
					_config.Mrs.IgnoredExtraFeatures = value.GetVsymStrings().ToArray();
					break;
				case "mrs-feat-priority-list":
					_config.Mrs.FeatPriorityList = value.GetVsymStrings().ToArray();
					break;
				case "mrs-extra-priority-list":
					_config.Mrs.ExtraPriorityList = value.GetVsymStrings().ToArray();
					break;

				case "sem-relation-suffix":
					_config.Mrs.RelationSuffix = value;
					break;

				case "rel-name-path":
					_config.Mrs.RelPredPath = GetVsymPath(value);
					break;
				case "rel-handel-path":
					_config.Mrs.RelLblPath = GetVsymPath(value);
					break;

				case "event-type":
					_config.Mrs.EventType = value.GetVsymString();
					break;
				case "individual-type":
					_config.Mrs.IndividualType = value.GetVsymString();
					break;
				case "full_ref-ind-type":
					_config.Mrs.FullRefIndType = value.GetVsymString();
					break;
				case "ref-ind-type":
					_config.Mrs.RefIndType = value.GetVsymString();
					break;
				case "non_expl-ind-type":
					_config.Mrs.NonExplIndType = value.GetVsymString();
					break;
				case "deg-ind-type":
					_config.Mrs.DegIndType = value.GetVsymString();
					break;
				case "conj-ind-type":
					_config.Mrs.ConjIndType = value.GetVsymString();
					break;
				case "handle-type":
					_config.Mrs.HandleType = value.GetVsymString();
					break;
				case "non_event-type":
					_config.Mrs.NonEventType = value.GetVsymString();
					break;
				case "event_or_index-type":
					_config.Mrs.EventOrIndexType = value.GetVsymString();
					break;

				case "duplicate-lex-ids":
					_config.Generator.DuplicateLexIds.AddRange(value.GetVsymStrings());
					break;
				case "gen-ignore-rules":
					_config.Generator.Chart.IgnoreRules.AddRange(value.GetVsymStrings());
					break;
				case "parse-ignore-rules":
					_config.Parser.Chart.IgnoreRules.AddRange(value.GetVsymStrings());
					break;

				//case "mrs-rel-cfrom-feature":
				//    gc.mrs.cfrom = value;
				//    break;
				//case "mrs-rel-cto-feature":
				//    gc.mrs.cto = value;
				//    break;

				case "lexicon-tokens-path":
					_config.TokenMapping.LexiconTokensPath = new BootstrapFsPath(this, value);
					break;
				case "lexicon-last-token-path":
					_config.TokenMapping.LexiconLastTokenPath = new BootstrapFsPath(this, value);
					break;
				case "token-form-path":
					_config.TokenMapping.TokenFormPath = new BootstrapFsPath(this, value);
					break;
				case "token-carg-path":
					_config.TokenMapping.TokenCargPath = new BootstrapFsPath(this, value);
					break;
				case "token-id-path":
					_config.TokenMapping.TokenIdPath = new BootstrapFsPath(this, value);
					break;
				case "token-from-path":
					_config.TokenMapping.TokenFromPath = new BootstrapFsPath(this, value);
					break;
				case "token-to-path":
					_config.TokenMapping.TokenToPath = new BootstrapFsPath(this, value);
					break;
				case "token-postags-path":
					_config.TokenMapping.TokenPostagsPath = new BootstrapFsPath(this, value);
					break;
				case "token-posprobs-path":
					_config.TokenMapping.TokenPosprobsPath = new BootstrapFsPath(this, value);
					break;

				case "chart-mapping-context-path":
					_config.TokenMapping.ChartMappingContextPath = new BootstrapFsPath(this, value);
					break;
				case "chart-mapping-input-path":
					_config.TokenMapping.ChartMappingInputPath = new BootstrapFsPath(this, value);
					break;
				case "chart-mapping-output-path":
					_config.TokenMapping.ChartMappingOutputPath = new BootstrapFsPath(this, value);
					break;
				case "chart-mapping-position-path":
					_config.TokenMapping.ChartMappingPositionPath = new BootstrapFsPath(this, value);
					break;

				/// DEPRECATED:
				case "main-semantics-path":
				case "hole-type":
				case "label-type":
					break;

				///NOT RELEVANT:
				case "display-type-hierarchy-on-load":
					break;

				default:
					Debug.WriteLine("Ignoring option '*{0}* = {1}' in '{2}'", param, value, fn);
					break;
			}
		}
		public BootstrapFsPath GetVsymPath(String value)
		{
			return new BootstrapFsPath(this, value.GetVsymStrings());
		}
	};

	static class LispExt
	{
		public static readonly Dictionary<String, Char> lisp_char_codes = new Dictionary<String, char>
		{
			{"space",' '},
			{"fullwidth_question_mark",'\uFF1F'},
			{"horizontal_ellipsis",'\u2026'},
			{"fullwidth_full_stop",'\uFF0E'},
			{"fullwidth_exclamation_mark",'\uFF01'},
			{"black_circle",'\u25CF'},
			{"fullwidth_comma",'\uFF0C'},
			{"ideographic_full_stop",'\u3002'},
			{"white_circle",'\u25CB'},
			{"katakana_middle_dot",'\uFF65'},
			{"ideographic_space",'\u3000'},
		};

		public static IEnumerable<String> LispInsulatedSplit(this String s, Char ch_split)
		{
			int cb, i, i_last = 0;
			Char prev = default(Char);
			int nest = 0;
			bool f_q = false;
			for (i = 0; i < s.Length; i++)
			{
				Char ch = s[i];
				if (ch == '"' && prev != '\\')
					f_q = !f_q;
				else if (ch == '(' && prev != '\\')
					nest++;
				else if (ch == ')' && prev != '\\' && nest > 0)
					nest--;
				else if (nest == 0 && !f_q && ch == ch_split)
				{
					if ((cb = i - i_last) > 0)
						yield return s.Substring(i_last, cb);
					i_last = i + 1;
				}
				prev = ch;
			}
			if ((cb = i - i_last) > 0)
				yield return s.Substring(i_last, cb);
		}

		public static String GetVsymString(this String value)
		{
			if (value.Length == 0)
				return String.Empty;

			if (value.Contains("vsym"))
				return value.SplitNoEmpty(',', ' ', '(', ')', '\"').Where(s => s != "vsym").First();
			else
				return value;
		}

		public static IEnumerable<String> GetVsymStrings(this String value)
		{
			if (value.Length == 0)
				yield break;

			if (value[0] == ',')
				value = value.Substring(1);
			else if (value.StartsWith("list"))
				value = value.Substring(4).Trim();
			else if (value.StartsWith("append"))
				throw new Exception("Cannot process lisp 'append' keyword in configuration file.");

			String q = null;
			foreach (String s in value.SplitNoEmpty(',', ' ', '(', ')', '\"'))
				if (s != "vsym")
					yield return q = s;
			if (q == null)
				yield return value;
		}

		public static IEnumerable<Pairing<String>> GetTuples(String filename)
		{
			using (StreamReader sr = new StreamReader(filename))
			{
				String r_line, line = String.Empty;
				int line_no = 0;
			next_line:
				while ((r_line = sr.ReadLine()) != null)
				{
					line_no++;
					r_line = r_line.Trim();
					if (r_line.Length == 0)
						continue;

					if (r_line == "#|")
					{
						while ((r_line = sr.ReadLine()) != null)
						{
							line_no++;
							if ((r_line = r_line.Trim()) == "|#")
								goto next_line;
						}
						throw new Exception("Error: end of file encountered looking for end of block comment '|#'");
					}
					if (r_line[0] == ';')
					{
						//if (line.Length > 0)
						//    throw new ConfigException("Error: ';' in {0}, line {1}", file, line_no);
						line += " ";
						continue;
					}
					line += " " + r_line;

					int nest = 0;
					Char prev = default(Char);
					for (int i = 0; i < line.Length; i++)
					{
						if (line[i] == '(' && prev != '\\')
							nest++;
						if (line[i] == ')' && prev != '\\')
						{
							if (nest == 0)
								throw new ConfigException("Error: ')' in {0}, line {1}", filename, line_no);
							nest--;
						}
						prev = line[i];
					}
					// incomplete line
					if (nest != 0)
						continue;

					line = line.Trim();
					if (line == String.Empty)
						continue;

					if (line[0] != '(' || line[line.Length - 1] != ')')
						throw new ConfigException("Error: '{0}' in {1}, line {2}", line[0], filename, line_no);
					line = line.Substring(1, line.Length - 2);

					String[] rgs = line.ToLower().LispInsulatedSplit(' ').ToArray();

					if (rgs[0] == "defparameter" || rgs[0] == "def-lkb-parameter" || rgs[0] == "setf")
					{
						if (rgs.Length > 4)
							throw new ConfigException("Error: in {0}, line {1}; too many arguments for {2}", filename, line_no, rgs[0]);
						if (rgs.Length < 3)
							throw new ConfigException("Error: in {0}, line {1}; not enough arguments for {2}", filename, line_no, rgs[0]);

						String v = rgs[2];
						if (v.StartsWith("(append ") && v.EndsWith(")"))
						{
							var x = v.Substring(8, v.Length - 9).LispInsulatedSplit(' ').ToArray();
							if (rgs[1] == x[0])
								v = x[1];
						}
						yield return new Pairing<String>(rgs[1], v);
					}
					else if (rgs[0] == "in-package")
					{
					}
					else
						throw new ConfigException("Error: '{0}' in {1}, line {2}; expected 'defparameter', 'def-lkb-parameter', or 'setf'.", rgs[0], filename, line_no);

					line = String.Empty;
				}

				if (line.Trim().Length > 0)
					throw new ConfigException("Error: unexpected end of file {0}; expected ')'.", filename);
			}
		}
	};
}